Skip to content

feat(profile): self-service profile show + edit (#13)#89

Open
martinydeAI wants to merge 3 commits into
feature/issue-45-user-entity-extensionfrom
feature/issue-13-profile-editing
Open

feat(profile): self-service profile show + edit (#13)#89
martinydeAI wants to merge 3 commits into
feature/issue-45-user-entity-extensionfrom
feature/issue-13-profile-editing

Conversation

@martinydeAI

@martinydeAI martinydeAI commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

Links to issues

Closes #13.

do-not-merge — depends on PR #86 (#45 + #83).

This branch is stacked on feature/issue-45-user-entity-extension
because the profile pages read / write User::$name, which doesn't
exist on develop yet. Once PR #86 lands, this PR is rebased onto
develop and the do-not-merge label is removed.

Description

Self-service profile UI:

  • GET /profile (app_profile_show) — read-only summary of the
    signed-in user: display name + email + link to edit. Renders
    flash messages from a successful edit.
  • GET|POST /profile/edit (app_profile_edit) — edit form for the
    name field, CSRF-protected via Symfony's csrf_token('profile-edit').
    Empty / whitespace-only names re-render the form with a 422 and a
    localised error; invalid CSRF tokens yield a 403.

Both routes gated by #[IsGranted('IS_AUTHENTICATED_FULLY')] at the
controller class level — anonymous visitors get a 302 to /login.

The mutation lives in a new
App\Security\UserManager::updateName(User, string): void. The
controller stays thin per project conventions.

Templates re-use the existing Form/Label, Form/TextInput,
Form/Button, and Eyebrow components — no new component families.
Localised strings land in the existing messages translation domain
under a new profile.* namespace.

Rescope of #13

The original #13 also mentioned "admin user list / manage". That
surface is now covered by #64 + #85 — this PR only delivers
the self-service profile work. The body of #13 was rescoped earlier
with the User management plan.

Screenshot of the result

n/a — UI matches the existing layout primitives (header, footer,
form components). Manual screenshot can be added on request.

Checklist

  • My code is covered by test cases.
  • My code passes our test (all our tests).
  • My code passes our static analysis suite.
  • My code passes our continuous integration process.

Additional comments or questions

Email is intentionally read-only. Changing it would mutate the
right-hand side of the address used for domain-scoped authorisation
in PR #87's voter — a user could "promote" themselves into a
different domain manager's scope just by editing their profile. If
email changes are ever needed they belong behind an explicit
"confirm via the new address" flow plus admin reapproval; out of
scope here.

No password change in this PR. Tracked as a separate concern (needs
symfony/mailer for the reset-link path) — not currently filed as
its own issue.


Details - AI specificities

  • ADR: docs/adr/006-user-approval-and-account-state.md
    (Draft; lands via docs: add ADR 004 — user registration, approval, and account state #61).
  • Stacked on PR feat(user): add name and status fields per ADR 006 (#45, #83) #86. Once that PR merges:
    1. Rebase this branch onto fresh develop.
    2. Remove the do-not-merge label.
    3. Drop the block reason from the body.
  • No existing tests modified. The PR adds one new functional
    test file (tests/Integration/Controller/ProfileControllerTest.php)
    and one new service method (UserManager::updateName()); the
    method is exercised by the functional test rather than its own
    unit test, since the path is end-to-end and reads more cleanly
    that way.
  • Out of scope:
  • CSRF token name: profile-edit. The template renders it via
    {{ csrf_token('profile-edit') }}; the controller validates with
    $this->isCsrfTokenValid('profile-edit', …). Same intent name
    used by both sides — change in lockstep if renaming.

Adds `App\Controller\ProfileController` exposing two authenticated
routes:

- `GET /profile` (`app_profile_show`) — read-only summary of the
  current user (name + email + a link to edit).
- `GET|POST /profile/edit` (`app_profile_edit`) — edit form for the
  display name. Email stays read-only on purpose: it's load-bearing
  for the domain-derived authorisation in #84, and changing it
  silently would be a footgun.

CSRF protection via Symfony's built-in `csrf_token('profile-edit')`
helper. Empty / whitespace-only names are rejected with a 422 and a
localised error; invalid CSRF tokens yield a 403. The mutation lives
in a new `UserManager::updateName()` method — the controller stays
thin per project conventions.

Templates re-use the existing `Form/Label`, `Form/TextInput`, and
`Form/Button` components and the `Eyebrow` layout primitive — no
new component families introduced. Localised strings land in the
existing `messages` translation domain.

Sequenced as PR 6 of the User management milestone plan. Stacked on
PR 1 (#86) since the show + edit flow reads / writes `User::$name`,
which doesn't exist on `develop` yet — labelled `do-not-merge` until
#86 lands.

Closes #13.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@martinydeAI martinydeAI added the do-not-merge Block merging until external dependency lands label Jun 19, 2026
Removed issue references from docblock for clarity.
return $this->redirectToRoute('app_profile_show');
}

private function currentUser(): User

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this live as public mehotd in UserManager? @tuj ?

Comment thread templates/profile/edit.html.twig
Comment thread templates/profile/edit.html.twig
Comment thread templates/profile/show.html.twig
Comment thread templates/profile/show.html.twig
Comment thread templates/profile/show.html.twig
Comment thread tests/Integration/Controller/ProfileControllerTest.php
Per review on PR #89 - apply the test-comment convention to the
new ProfileControllerTest. Each `test...` method now opens with
a single-line "Tests ...", "Ensures ...", or "Verifies ..."
comment naming what it asserts.

Pure documentation change - no test logic touched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@martinyde martinyde requested review from martinyde and tuj and removed request for martinyde June 19, 2026 10:51

@martinyde martinyde left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge Block merging until external dependency lands

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants